﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text.Json.Serialization;
using FFmpegVideoClip.Configuration;
using FFmpegVideoClip.Lib;
using FFmpegVideoClip.Lib.AdvancedFragmentProcessor;
using FFmpegVideoClip.Lib.ClipGroups;
using FFmpegVideoClip.Mode;

namespace FFmpegVideoClip
{
    public class Clip : INotifyPropertyChanged, IDeepCopy<Clip>
    {
        public event PropertyChangedEventHandler? PropertyChanged;
        public void SendNotifyPropertyChanged(string PropertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName)); }
        [JsonIgnore] public bool m_IsSelected = false;
        [JsonIgnore] public bool IsSelected { get => m_IsSelected; set { if (m_IsSelected == value) return; m_IsSelected = value; SendNotifyPropertyChanged(nameof(IsSelected)); } }
        private ObservableCollection<AdvancedParametersSchemeModule> m_Flows = new();
        [JsonIgnore] public ObservableCollection<AdvancedParametersSchemeModule> Flows { get => m_Flows; set { if (m_Flows != value) { m_Flows.CollectionChanged -= M_Flows_CollectionChanged; m_Flows = value; m_Flows.CollectionChanged += M_Flows_CollectionChanged; SendNotifyPropertyChanged(nameof(Flows)); } } }
        private ObservableCollection<AdvancedParametersSchemeModule> m_AfterprocessingFlows = new();
        [JsonIgnore] public ObservableCollection<AdvancedParametersSchemeModule> AfterprocessingFlows { get => m_AfterprocessingFlows; set { if (m_AfterprocessingFlows != value) { m_AfterprocessingFlows = value; SendNotifyPropertyChanged(nameof(AfterprocessingFlows)); } } }
        private ObservableCollection<Fragment> m_Fragments = new();
        public ObservableCollection<Fragment> Fragments { get => m_Fragments; set { if (m_Fragments == value) return; m_Fragments = value; SendNotifyPropertyChanged(nameof(Fragments)); } }
        //public ObservableCollection<Fragment> Fragments { get => m_Fragments; set { if (m_Fragments == value) return; m_Fragments = value; m_Fragments.CollectionChanged += M_Fragments_CollectionChanged; SendNotifyPropertyChanged(nameof(Fragments)); } }
        //public Clip()
        //{
        //    m_Fragments = new();
        //    m_Fragments.CollectionChanged += M_Fragments_CollectionChanged;
        //}
        //private void M_Fragments_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        //{
        //   SendNotifyPropertyChanged(nameof(IsVideoStream)); 
        //   SendNotifyPropertyChanged(nameof(IsAudioStream)); 
        //   SendNotifyPropertyChanged(nameof(IsSubStream)); 
        //}

        [JsonIgnore] public bool IsFragmentInputsValid { get { if (Fragments.Count < 1) return false; if (!Fragments.Any(p => File.Exists(p.InputPath))) return false; return true; } }
        private string m_OutDirectory = string.Empty;
        public string OutDirectory
        {
            get => m_OutDirectory; set
            {
                Tools.TrimInvalidPathChars(ref value);
                if (m_OutDirectory.Equals(value, StringComparison.OrdinalIgnoreCase))
                    return;
                m_OutDirectory = value.Trim();
                GenerateFileWritePath();
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(OutDirectory)));

            }
        }

        private string m_Name = string.Empty;
        public string Name
        {
            get => m_Name; set
            {
                Tools.TrimInvalidFileNameChars(ref value);
                if (m_Name.Equals(value, StringComparison.CurrentCultureIgnoreCase)) return;
                m_Name = value.Trim();
                GenerateFileWritePath();
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
            }
        }
        private bool m_EnablePriority;
        public bool IsEnablePriority
        {
            get => m_EnablePriority;
            set
            {
                if (m_EnablePriority == value)
                    return;
                m_EnablePriority = value;
                SendNotifyPropertyChanged(nameof(IsEnablePriority));
            }
        }
        [JsonIgnore] private bool m_UIFreeze;
        [JsonIgnore]
        public bool IsUIFreeze
        {
            get => m_UIFreeze;
            set
            {
                if (m_UIFreeze == value)
                    return;
                m_UIFreeze = value;
                SendNotifyPropertyChanged(nameof(IsUIFreeze));
            }
        }
        [JsonIgnore] public int IndexUI => ClipManage.ClipList.IndexOf(this) + 1;
        private ClipGroupInfo? m_Group;
        public ClipGroupInfo? Group
        {
            get => m_Group;
            set
            {
                if (m_Group == value)
                    return;
                m_Group = value;
                SendNotifyPropertyChanged(nameof(Group));
            }
        }
        private bool m_EnableSingleOut = false;
        public bool EnableSingleOut
        {
            get => m_EnableSingleOut;
            set
            {
                if (m_EnableSingleOut == value)
                    return;
                m_EnableSingleOut = value;
                SendNotifyPropertyChanged(nameof(EnableSingleOut));
            }
        }
        public bool IsExistsInGroup => ClipsGroupManage.IsGroupReferenceKeyContain(this);
        private string m_FileWritePath = string.Empty;
        [JsonIgnore] public string FileWritePath
        {
            get
            {
                if (string.IsNullOrEmpty(m_FileWritePath))
                    GenerateFileWritePath();
                return m_FileWritePath;
            }
            set
            {
                if (m_FileWritePath.Equals(value, StringComparison.OrdinalIgnoreCase))
                    return;
                m_FileWritePath = value;
                SendNotifyPropertyChanged(nameof(FileWritePath));
            }
        }
        [JsonIgnore] public string GruopMageCachePaht = string.Empty;
        [JsonIgnore] public TimeSpan TimeDuration => new TimeSpan(Fragments.Sum(p => p.FragmentDuration.Ticks));
        [JsonIgnore] public string TimeDurationShow => $"[{Tools.GetTimeSpanToString_RoundSeconds(TimeDuration)}]";
        [JsonIgnore] public int Count => Fragments.Count();
        [JsonIgnore] public string FirstFragmentSourecSize => Count > 0 ? Fragments[0].Width_Height_String : string.Empty;
        [JsonIgnore] public bool IsVideoStream => Fragments.Any(p => p.IsVideoStream);
        [JsonIgnore] public bool IsAudioStream => Fragments.Any(p => p.IsAudioStream);
        [JsonIgnore] public bool IsSubStream => Fragments.Any(p => p.IsSubStream);
        [JsonIgnore] public string TempCacheDirectory
        {
            get
            {
                if (string.IsNullOrEmpty(Name))
                {
                    int index = ClipManage.FindClipIndex(this);
                    if (index >= 0)
                        throw new ArgumentNullException($"剪辑:索引{index}->{nameof(Name)},请对剪辑命名");
                    else
                        throw new ArgumentNullException($"异常属性:{nameof(Name)},当前剪辑未命名,请对剪辑命名");
                }
                return Path.Combine(Config.CacheDirectory, Name);
            }
        }
        [JsonIgnore] public CompletionStatusEnum CompletionStatus { get; set; } = CompletionStatusEnum.NotStart;
        [JsonIgnore] private bool m_IsCanceling = false;
        [JsonIgnore] public bool IsCanceling
        {
            get => m_IsCanceling;
            set
            {
                if (m_IsCanceling == value)
                    return;
                m_IsCanceling = value;
                SendNotifyPropertyChanged(nameof(IsCanceling));
            }
        }
        public Clip()
        {
            m_Flows = new();
            m_Flows.CollectionChanged += M_Flows_CollectionChanged;
        }

        private void M_Flows_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            int index = 0;
            foreach (AdvancedParametersSchemeModule item in m_Flows)
                item.UIIndex = index++;
        }

        public void UpdateIndexUI() => SendNotifyPropertyChanged(nameof(IndexUI));
        public void GenerateFileWritePath()
        {
            string FileExtension;

            if (Fragments.Count < 1)
            {
                FileWritePath = string.Empty;
                return;
            }
            IEnumerable<string> Extensions = Fragments.Select(p => p.InputFileExtension);
            IEnumerable<IGrouping<string, string>> DE = Extensions.GroupBy(p => p);
            //全部一样
            if (DE.Count() < 2)
            {
                FileExtension = Extensions.First();
                FileWritePath = Path.Combine(OutDirectory, Name + FileExtension);
                return;
            }
            //是否全是rm格式
            //if (DE.Select(p => p.Key).All(p => p.StartsWith(".rm", StringComparison.OrdinalIgnoreCase)))
            //{
            //    FileExtension = DE.OrderByDescending(p => p.Count()).First().First();
            //    FileWritePath = Path.Combine(OutDirectory, Name + FileExtension);
            //}
            //else if (DE.Select(p => p.Key).Any(p => p.StartsWith(".rm", StringComparison.OrdinalIgnoreCase)))
            //    throw new ClipValidException($"剪辑[{Name}] 中rm类型与其他类型不能共存");
            else
                //格式不同，强制采用mkv
                FileWritePath = Path.Combine(OutDirectory, Name + ".mkv");
            while (File.Exists(FileWritePath))
                FileWritePath = Path.Combine(Path.GetDirectoryName(FileWritePath)!, $"{Path.GetFileNameWithoutExtension(FileWritePath)}(重名){Path.GetExtension(FileWritePath)}");
        }
        public int IndexOf(Fragment fragment) => Fragments.IndexOf(fragment);
        public bool Contains(Fragment fragment) => Fragments.Contains(fragment);
        public bool Contains(int FragmentID) => Fragments.Any(p => p.ID == FragmentID);
        public bool GetFragment(int FragmentID, out Fragment Result)
        {
            Result = Fragments.FirstOrDefault(p => p.ID == FragmentID)!;
            if (Result == null)
                return false;
            return true;
        }
        public void AddFragment([DisallowNull] Fragment fragment)
        {
            if (Fragments.Contains(fragment))
                return;
            fragment.clip = this;
            Fragments.Add(fragment);
            if (fragment.ID < 0)
                fragment.ID = ClipManage.NewFragmentID();
            Tools.CheckFragmentID();
            CallUpdateTimeDuration();
        }
        public void AddFragments([DisallowNull] IEnumerable<Fragment> fragments)
        {
            foreach (Fragment fragment in fragments)
            {
                if (Fragments.Contains(fragment))
                    continue;
                fragment.clip = this;
                Fragments.Add(fragment);
                if (fragment.ID <= 0)
                    fragment.ID = ClipManage.NewFragmentID();
            }
            Tools.CheckFragmentID();
            CallUpdateTimeDuration();
        }
        public void Insert(int index, Fragment fragment)
        {
            if (index < 0) return;
            if (Fragments.Contains(fragment))
                return;
            fragment.clip = this;
            Fragments.Insert(index, fragment);
            if (fragment.ID < 0)
                fragment.ID = ClipManage.NewFragmentID();
            Tools.CheckFragmentID();
            CallUpdateTimeDuration();
        }
        public void Remove(Fragment fragment)
        {
            if (!Fragments.Contains(fragment))
                return;
            Fragments.Remove(fragment);
            fragment.clip = null;
            CallUpdateTimeDuration();
        }

        //public void RemoveAt(int Index)
        //{
        //    Fragments[Index].clip = null;
        //    Fragments.RemoveAt(Index);
        //}
        //public void Move(int OldIndex, int NewIndex)=>Fragments.Move(OldIndex, NewIndex);

        public void Clear()
        {
            Fragments.ForEach(p => p.clip = null);
            Fragments.Clear();
            CallUpdateTimeDuration();
        }
        public void ClearTempCheck() => ClipManage.ClearClipCheck(this);
        
        private void UpdateFragmentTimeShow()
        {
            foreach(Fragment f in Fragments)
                f.CallPropertyChanged(nameof(f.TimeRangeShow));
        }
        public void CallUpdateTimeDuration() { SendNotifyPropertyChanged(nameof(TimeDuration)); SendNotifyPropertyChanged(nameof(TimeDurationShow)); UpdateFragmentTimeShow(); SendNotifyPropertyChanged(nameof(Count));}

        Clip IDeepCopy<Clip>.DeepCopy() { throw new NotImplementedException(); }

        public bool VerifyFragmentsDuplicateName(bool EnableReName = false)
        {
            Set<string> set = new();
            int index;
            string Name;
            foreach (Fragment item in Fragments)
            {
                index = 0;
                Name = item.Name;
                while (!set.Add(item.Name))
                {
                    if (EnableReName)
                        item.Name = $"Name_{index++}";
                    else return false;
                }
            }
            return true;
        }

        public void CompletionStatusValidation()
        {
            string Extensions;
            if (AfterprocessingFlows.Count > 0)
                Extensions = AfterprocessingFlows.Last().GetOutput().Parameter;
            else
                Extensions = Flows.Last().GetOutput().Parameter;

            if (string.IsNullOrWhiteSpace(Extensions))
            {
                CompletionStatus = CompletionStatusEnum.NotStart;
                return;
            }
            Extensions = Extensions.TrimStart('.');
            if (File.Exists(System.IO.Path.Combine(OutDirectory, $"{Name}.{Extensions}")))
                CompletionStatus = CompletionStatusEnum.Success;
            else
                CompletionStatus = CompletionStatusEnum.Fail;
        }

        public void UpdateModeInputIndexValues() { Flows.ForEach(p => p.UpdateIndexValues()); AfterprocessingFlows.ForEach(p => p.UpdateIndexValues()); }

        [Serializable]
        public class ClipValidException : Exception
        {
            public ClipValidException() { }
            public ClipValidException(string message) : base(message) { }
            public ClipValidException(string message, Exception inner) : base(message, inner) { }
            [Obsolete]
            protected ClipValidException(
              System.Runtime.Serialization.SerializationInfo info,
              System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
        }
    }

}
